#include <QJsonObject>
#include <QJsonArray>

#include <osg/Geometry>

#include <Geom_BSplineCurve.hxx>
#include <GeomAdaptor_Curve.hxx>
#include <GCPnts_TangentialDeflection.hxx>
#include <GCPnts_UniformDeflection.hxx>
#include <CPnts_AbscissaPoint.hxx>

#include "Spline3D.h"
#include "JsonReader.h"
#include "GLSLPrograms.h"
#include "OsgUtil.h"
#include "QtJson.h"

// 类似于createEdgeProgram, 除了z值处理之外
osg::ref_ptr<osg::Program> createEdgeProgram2();

struct Spline3DPrivate
{
	Spline3DPrivate()
		: m_rep3D(new osg::Geometry())
	{
		m_rep3D->setNodeMask(D3CurveMask);
		osg::ref_ptr<osg::Program> program = createEdgeProgram2();
		auto ss = m_rep3D->getOrCreateStateSet();
		ss->setAttributeAndModes(program, osg::StateAttribute::ON);
	}
	osg::ref_ptr<osg::Geometry> m_rep3D;
	std::vector<osg::Vec3d> m_points; // 曲线上的点，缓存数据

	// 样条线的定义
	int m_Flag;
	int m_Degree;
	std::vector<double> m_Knots;
	std::vector<double> m_Weights;
	std::vector<osg::Vec3d> m_ControlPoints;

	osg::Vec4 m_color = { 1, 1, 1, 1 };
	bool m_selected = false;
};

Spline3D::Spline3D(INode* parent)
	: Ent3DCurve(parent)
	, d_ptr(new Spline3DPrivate)
{
}

void Spline3D::setFlag(int flag)
{
	if (d_ptr->m_Flag != flag)
	{
		d_ptr->m_Flag = flag;
		addFlags(UPDATE_GEOMETRY);
	}
}

void Spline3D::setDegree(int degree)
{
	if (d_ptr->m_Degree != degree)
	{
		d_ptr->m_Degree = degree;
		addFlags(UPDATE_GEOMETRY);
	}
}

void Spline3D::setCtrlPoints(const std::vector<osg::Vec3d> &pos)
{
	if (d_ptr->m_ControlPoints != pos)
	{
		d_ptr->m_ControlPoints = pos;
		addFlags(UPDATE_GEOMETRY);
	}
}

void Spline3D::setKnots(const std::vector<double>& knots)
{
	if (d_ptr->m_Knots != knots)
	{
		d_ptr->m_Knots = knots;
		addFlags(UPDATE_GEOMETRY);
	}
}

void Spline3D::setWeights(const std::vector<double>& weights)
{
	if (d_ptr->m_Weights != weights)
	{
		d_ptr->m_Weights = weights;
		addFlags(UPDATE_GEOMETRY);
	}
}

void Spline3D::setColor(const osg::Vec4 &color)
{
	if (d_ptr->m_color != color)
	{
		d_ptr->m_color = color;
		addFlags(UPDATE_GEOMETRY);
	}
}

int Spline3D::getFlag() const
{
	return d_ptr->m_Flag;
}

int Spline3D::getDegree() const
{
	return d_ptr->m_Degree;
}

const std::vector<osg::Vec3d>& Spline3D::getCtrlPoints() const
{
	return d_ptr->m_ControlPoints;
}

const std::vector<double>& Spline3D::getKnots() const
{
	return d_ptr->m_Knots;
}

const std::vector<double>& Spline3D::getWeights() const
{
	return d_ptr->m_Weights;
}

const osg::Vec4& Spline3D::color() const {
	return d_ptr->m_color;
}

std::vector<osg::Vec3d> convert(const TColgp_Array1OfPnt& points)
{
	std::vector<osg::Vec3d> pointsV;
	for (int i = points.Lower(); i <= points.Upper(); ++i)
	{
		const auto& pt = points(i);
		pointsV.push_back(osg::Vec3d(pt.X(), pt.Y(), pt.Z()));
	}
	return pointsV;
}

TColgp_Array1OfPnt convert(const std::vector<osg::Vec3d>& points)
{
	TColgp_Array1OfPnt pointsA(1, points.size());
	for (int i = 0; i < points.size(); ++i)
	{
		const auto& cp = points[i];
		pointsA.SetValue(i + 1, gp_Pnt(cp.x(), cp.y(), cp.z()));
	}
	return pointsA;
}

std::vector<double> convert(const TColStd_Array1OfReal& weights)
{
	std::vector<double> weightsV;
	for (int i = weights.Lower(); i <= weights.Upper(); ++i)
	{
		auto w = weights(i);
		weightsV.push_back(w);
	}
	return weightsV;
}

TColStd_Array1OfReal convert(const std::vector<double>& weights)
{
	TColStd_Array1OfReal weightsA(1, weights.size());
	for (int i = 0; i < weights.size(); ++i)
	{
		weightsA.SetValue(i + 1, weights[i]);
	}
	return weightsA;
}

std::vector<int> convert(const TColStd_Array1OfInteger& mults)
{
	std::vector<int> multsV;
	for (int i = mults.Lower(); i <= mults.Upper(); ++i)
	{
		auto w = mults(i);
		multsV.push_back(w);
	}
	return multsV;
}

TColStd_Array1OfInteger convert(const std::vector<int>& mults)
{
	TColStd_Array1OfInteger multsA(1, mults.size());
	for (int i = 0; i < mults.size(); ++i)
	{
		multsA.SetValue(i + 1, mults[i]);
	}
	return multsA;
}

void toOCCTKnots(const std::vector<double>& acadKnots, std::vector<double>& knots, std::vector<int>& mults)
{
	int mult = 1;
	for (int i = 0; i < int(acadKnots.size() - 1); ++i)
	{
		if (acadKnots[i + 1] - acadKnots[i] <= Epsilon(Abs(acadKnots[i]))) {
			++mult;
		}
		else
		{
			knots.push_back(acadKnots[i]);
			mults.push_back(mult);
			mult = 1;
		}
	}
	if (!acadKnots.empty())
	{
		knots.push_back(acadKnots[acadKnots.size() - 1]);
		mults.push_back(mult);
	}
}

void toACADKnots(const std::vector<double>& knots, const std::vector<int>& mults, std::vector<double>& acadKnots)
{
	auto m = mults;
	m.resize(knots.size(), 1);
	for (int i = 0; i < knots.size(); ++i)
	{
		for (int j = 0; j < m[i]; ++j)
		{
			acadKnots.push_back(knots[i]);
		}
	}
}

Handle(Geom_BSplineCurve) getCurve(int m_Flag,
int m_Degree,
const std::vector<double>& m_Knots,
std::vector<double>& m_Weights,
const std::vector<osg::Vec3d>& m_ControlPoints
)
{
	// 计算m_points
	int cpNum = m_ControlPoints.size();
	TColgp_Array1OfPnt ctrlPoints = convert(m_ControlPoints);
	m_Weights.resize(cpNum, 1.0);
	TColStd_Array1OfReal weights = convert(m_Weights);
	std::vector<double> kv;
	std::vector<int> mv;
	toOCCTKnots(m_Knots, kv, mv);
	TColStd_Array1OfReal knots = convert(kv);
	TColStd_Array1OfInteger mults = convert(mv);
	Handle(Geom_BSplineCurve) curve = new Geom_BSplineCurve(ctrlPoints, weights, knots, mults, m_Degree);
	if (m_Flag & 2) //周期性样条曲线
		curve->SetPeriodic();
	// 封闭曲线 ?
	return curve;
}

void Spline3D::update3D()
{
	if (flags())
	{
		// 计算m_points
		int cpNum = d_ptr->m_ControlPoints.size();
		// 均匀折线化
		d_ptr->m_points.clear();
		try
		{
			Handle(Geom_BSplineCurve) curve = getCurve(d_ptr->m_Flag, d_ptr->m_Degree, d_ptr->m_Knots,
				d_ptr->m_Weights, d_ptr->m_ControlPoints);
			GCPnts_TangentialDeflection myAlgo2;
			double aDeflection = 3.0 / 180 * M_PI;
			GeomAdaptor_Curve adaptedCurve(curve);
			double len = 0;
			for (int i = 0; i < cpNum - 1; ++i)
			{
				const auto& det = d_ptr->m_ControlPoints[i + 1] - d_ptr->m_ControlPoints[i];
				len += sqrt(det.x() * det.x() + det.y() * det.y() + det.z() * det.z());
			}
			double Deflection = len / 200.0;
			myAlgo2.Initialize(adaptedCurve, aDeflection, Deflection);
			//if (myAlgo2.IsDone())
			{
				int nbr = myAlgo2.NbPoints();
				double param;
				gp_Pnt pt;
				for (int i = 1; i <= nbr; i++)
				{
					param = myAlgo2.Parameter(i);
					pt = myAlgo2.Value(i);
					//pt = pt.Transformed(L.Transformation());
					d_ptr->m_points.push_back(osg::Vec3d(pt.X(), pt.Y(), pt.Z()));
				}
			}
		}
		catch (Standard_Failure&)
		{
			d_ptr->m_points.clear();
		}

		// 组装OSG图形对象
		const osg::Vec4f& selectedColor = osg::Vec4f(0.f, 0.8f, 0.f, 1.f);
		auto va = new osg::Vec3Array();
		for (auto pt : d_ptr->m_points)
		{
			va->push_back(pt);
		}
		d_ptr->m_rep3D->setVertexArray(va);

		osg::ref_ptr<osg::Vec4Array> ca = new osg::Vec4Array();
	
		if (d_ptr->m_selected)
			ca->push_back(selectedColor);
		else
			ca->push_back(d_ptr->m_color);
		
		d_ptr->m_rep3D->setColorArray(ca.get(), osg::Array::BIND_OVERALL);

		d_ptr->m_rep3D->getPrimitiveSetList().clear();
		d_ptr->m_rep3D->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, d_ptr->m_points.size()));

		d_ptr->m_rep3D->dirtyGLObjects();
		removeFlags(0xFFFFFFFF);
	}
}

osg::Node * Spline3D::getRep3D() const
{
	return d_ptr->m_rep3D.get();
}

void Spline3D::setSelected(bool val)
{
	if (d_ptr->m_selected != val)
	{
		d_ptr->m_selected = val;
		const osg::Vec4f& selectedColor = osg::Vec4f(0.f, 0.8f, 0.f, 1.f);
		auto ca = dynamic_cast<osg::Vec4Array*>(d_ptr->m_rep3D->getColorArray());
		assert(ca);
		if (ca)
		{
			ca->clear();
			if (d_ptr->m_selected)
				ca->push_back(selectedColor);
			else
				ca->push_back(d_ptr->m_color);
			d_ptr->m_rep3D->setColorArray(ca, osg::Array::BIND_OVERALL);
			ca->dirty();
			d_ptr->m_rep3D->dirtyGLObjects();
		}
	}
}

void Spline3D::save(QJsonObject & json) const
{
	Ent3DCurve::save(json);

	json["color"] = saveVec4f(d_ptr->m_color);

	json["flag"] = d_ptr->m_Flag;
	json["degree"] = d_ptr->m_Degree;
	json["knots"] = saveDoubles(d_ptr->m_Knots);
	json["weights"] = saveDoubles(d_ptr->m_Weights);
	QJsonArray poles;
	for (const auto& pt : d_ptr->m_ControlPoints)
	{
		poles.append(saveVec3f(pt));
	}
	json["poles"] = poles;
}

void Spline3D::load(const QJsonObject& json, JsonReader& reader)
{
	Ent3DCurve::load(json, reader);

	d_ptr->m_color = loadVec4f(json["color"].toArray());

	d_ptr->m_Flag = json["flag"].toInt();
	d_ptr->m_Degree = json["degree"].toInt();
	d_ptr->m_Knots = loadDoubles(json["knots"].toArray());
	d_ptr->m_Weights = loadDoubles(json["weights"].toArray());
	QJsonArray poles = json["poles"].toArray();
	for (int i = 0; i < poles.count(); i++)
	{
		const auto& pole = poles.at(i);
		d_ptr->m_ControlPoints.push_back(loadVec3f(pole.toArray()));
	}
}

std::vector<osg::Vec3d> Spline3D::handles() const
{
	return d_ptr->m_ControlPoints;
}

void Spline3D::move(const osg::Vec3d& offset)
{
	for (int i = 0; i< d_ptr->m_ControlPoints.size();i++)
	{
		d_ptr->m_ControlPoints[i] = d_ptr->m_ControlPoints[i] + offset;
	}
	addFlags(UPDATE_GEOMETRY);
}

void Spline3D::scale(const osg::Vec3d & center, double scale)
{
	for (auto& pt : d_ptr->m_points)
	{
		pt = center + (pt - center) * scale;
	}
}

void Spline3D::stretch(int index, const osg::Vec3d &offset)
{
	d_ptr->m_ControlPoints[index] = d_ptr->m_ControlPoints[index] + offset;
	addFlags(UPDATE_GEOMETRY);
}

std::shared_ptr<Ent> Spline3D::clone() const
{
	auto spline = std::make_shared<Spline3D>(getParent());
	spline->copy(*this);
	return spline;
}

void Spline3D::copy(const Ent& ent)
{
	auto spline = dynamic_cast<const Spline3D*>(&ent);
	if (spline)
	{
		d_ptr->m_color = spline->color();
		d_ptr->m_Flag = spline->getFlag();
		d_ptr->m_Degree = spline->getDegree();
		d_ptr->m_ControlPoints = spline->getCtrlPoints();
		d_ptr->m_Knots = spline->getKnots();
		d_ptr->m_Weights = spline->getWeights();
		addFlags(UPDATE_GEOMETRY);
	}
}

TColgp_Array1OfPnt convert3D(const std::vector<osg::Vec3d>& points)
{
	TColgp_Array1OfPnt pointsA(1, points.size());
	for (int i = 0; i < points.size(); ++i)
	{
		const auto& cp = points[i];
		pointsA.SetValue(i + 1, gp_Pnt(cp.x(), cp.y(),cp.z()));
	}
	return pointsA;
}

std::vector<Handle(Geom_Curve)> Spline3D::getCurve3d() const
{
	// 计算m_points
	int cpNum = d_ptr->m_ControlPoints.size();
	TColgp_Array1OfPnt ctrlPoints = convert3D(d_ptr->m_ControlPoints);
	d_ptr->m_Weights.resize(cpNum, 1.0);
	TColStd_Array1OfReal weights = convert(d_ptr->m_Weights);
	std::vector<double> kv;
	std::vector<int> mv;
	toOCCTKnots(d_ptr->m_Knots, kv, mv);
	TColStd_Array1OfReal knots = convert(kv);
	TColStd_Array1OfInteger mults = convert(mv);
	Handle(Geom_BSplineCurve) curve = new Geom_BSplineCurve(ctrlPoints, weights, knots, mults, d_ptr->m_Degree);
	if (d_ptr->m_Flag & 2) //周期性样条曲线
		curve->SetPeriodic();
	// 封闭曲线 ?
	std::vector<Handle(Geom_Curve)> result;
	result.push_back(curve);
	return result;
}

void Spline3D::rotate(const double angle, const osg::Vec2d & basePoint)
{
	for (int i = 0; i < d_ptr->m_ControlPoints.size(); i++)
	{
		rotate2D(basePoint.x(), basePoint.y(), angle, d_ptr->m_ControlPoints[i]);
	}
	addFlags(UPDATE_GEOMETRY);
}
